
#include "UTCache.hpp"
#include "openvip/ovip.h"
#include "openvip_third/nutshell.h"
#include <cstdlib>
#include <queue>

using namespace ovip;

UTCache *dutp = nullptr;

void test_raw_cache(bool trace_mem = false)
{
    char **argv = new char *[1];
    argv[0]     = new char[50];
    strcpy(argv[0], "./build/bin/tb_mcv_nutshell_cache");
    UTCache dut(1, argv);
    dutp = &dut;

    std::function<int(bool)> stepfunc = [&dut](bool d) {
        dut.step(d);
        return 0;
    };
    XClock clk(stepfunc, {&dut.clock}, {&dut.port});

    dut.io_flush = 0;
    SimpleMemory goldn("golden", 4, 1024, 0xfa);
    SimpleRam ram(clk, "ram", dut.port, "io_out_mem_", 0xfa);
    SimpleRam mio(clk, "mio", dut.port, "io_mmio_", 0xfa);
    SimpleBusWrapper coh(dut.port, "io_out_coh");
    SimpleBusWrapper io(dut.port, "io_in_");
    CacheWrapper cache(&io, &clk, &dut.port);

    auto Write = [&cache, &goldn](u_int64_t addr, u_int64_t val) {
        Assert(cache.Write(addr, val), "Cache write failed!");
        goldn.Write(addr, 8, val);
    };

    std::srand(0);
    std::map<u_int32_t, u_int64_t> buffer;
    std::vector<u_int32_t> q_addr(10);
    std::vector<u_int32_t> q_cmd(10);
    int q_index = 0;
    Debug("Init Cache ...") cache.Init();

    for (int r = 0; r < 100; r++) {
        u_int64_t clk_start = clk.clk;
        u_int64_t tim_start = uTime();
        for (int i = 0; i < 4 * 1024; i++) {
            auto addr       = (u_int32_t)rand();
            addr            = addr - addr % 8;
            q_addr[q_index] = addr;

            auto iswt = (u_int32_t)rand();
            if (iswt % 2 == 0) {
                auto val = (u_int64_t)rand();
                Write(addr, val);
                if (trace_mem) buffer[addr] = val;
                q_cmd[q_index] = 1;
            } else {
                auto cdata     = cache.Read(addr);
                auto mdata     = goldn.Read(addr, 8);
                q_cmd[q_index] = 0;

                if (cdata != mdata) {
                    if (buffer.count(addr)) {
                        Error(
                            "find addr:0x%x, in buffer with val: 0x%lx,  c:0x%lx != m:0x%lx",
                            addr, buffer[addr], cdata, mdata);
                    } else {
                        if (!trace_mem) {
                            Warn(
                                "Trace mem is disabled, cannot check addr:0x%x (please set: trace_mem = true)",
                                addr);
                        } else {
                            Error(
                                "do not find addr: 0x%x in buffer! c:0x%lx != m:0x%lx",
                                addr, cdata, mdata);
                        }
                    }
                    Debug("last 10 cmd:");
                    for (int i = 1; i < 11; i++)
                        Debug("[%d] addr:0x%x, cmd:%s", i - 1,
                              q_addr[(i + q_index) % 10],
                              q_cmd[(i + q_index) % 10] == 0 ? "read" :
                                                               "write");
                }
                Assert(cdata == mdata,
                       "for addr:0x%x (%s), cache read(0x%lx) need be: 0x%lx",
                       addr,
                       addr < 0x30000000 || addr >= 0x80000000 ? "RAM" : "MMIO",
                       cdata, mdata);
            }

            q_index += 1;
            q_index %= 10;
        }
        Debug("do %d rand ramdom w/r 64bit data 4k times complete, speed: %.2f c/s", r, 1000000*(clk.clk - clk_start)/float(uTime() - tim_start));
    }
    Debug("Random read/write test complete: \n%s", cache.StatInfo().c_str());
    cache.ResetCount();
    for (int i = 0; i < 1024; i++) {
        ram.mMem.Write(i * 8, 8, i * 8);
        cache.Write(i * 8, i * 8);
    }
    for (int i = 0; i < 1024; i++) {
        auto cdata = cache.Read(i * 8);
        auto mdata = ram.mMem.Read(i * 8, 8);
        Assert(cdata == mdata, "cache read(0x%lx) need be: 0x%lx", cdata,
               mdata);
    }
    Debug("Sequential write/read test complete: \n%s",
          cache.StatInfo().c_str());
}

int main(int argsc, const char **argsv)
{
    Info("Test cache ...");
    // 注册 SIGINT 信号处理函数
    signal(SIGINT, [](int sig) {
        dutp->finalize();
        // delete dutp;
        Info("SIGINT(%d) received, exit!", sig);
        exit(0);
    });
    test_raw_cache();
    return 0;
}
